package tech.xs.common.framework.init.mq;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import tech.xs.common.framework.domain.constant.mq.QueueNames;
import tech.xs.common.framework.domain.enums.BaseEnum;
import tech.xs.common.framework.holder.SpringContextHolder;
import tech.xs.common.framework.init.InitExecute;
import tech.xs.common.framework.mq.MqMessageListener;
import tech.xs.common.framework.mq.SerialObjectInputStream;
import tech.xs.common.framework.util.MqUtil;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.*;

@Slf4j
@Component
@Order(300)
public class InitMq implements InitExecute {

    private static final List<Class<?>> FILTER_CLASS_LIST = new ArrayList<>();

    static {
        FILTER_CLASS_LIST.add(boolean.class);
        FILTER_CLASS_LIST.add(Boolean.class);
        FILTER_CLASS_LIST.add(int.class);
        FILTER_CLASS_LIST.add(Integer.class);
        FILTER_CLASS_LIST.add(long.class);
        FILTER_CLASS_LIST.add(Long.class);
        FILTER_CLASS_LIST.add(float.class);
        FILTER_CLASS_LIST.add(Float.class);
        FILTER_CLASS_LIST.add(double.class);
        FILTER_CLASS_LIST.add(Double.class);
        FILTER_CLASS_LIST.add(String.class);
        FILTER_CLASS_LIST.add(Number.class);
        FILTER_CLASS_LIST.add(BigDecimal.class);
        FILTER_CLASS_LIST.add(Class.class);
    }


    @Override
    public void execute() throws Exception {
        Set<String> queueNameList = new HashSet<>();
        queueNameList.add(QueueNames.AUTH_AUTH_PATH);
        queueNameList.add(QueueNames.DOC_SYNC_DOC);
        queueNameList.add(QueueNames.SYS_SYNC_DICT);

        Map<String, MqMessageListener> beansMap = SpringContextHolder.getBeansOfType(MqMessageListener.class);
        ArrayList<MqMessageListener> listeners = new ArrayList<>(beansMap.values());
        for (MqMessageListener listener : listeners) {
            setSerialObjectGenerics(listener);
            queueNameList.add(listener.getQueueName());
        }

        for (String queueName : queueNameList) {
            MqUtil.createDirectQueue(queueName);
        }

        for (MqMessageListener listener : listeners) {
            MqUtil.listener(listener);
        }
    }

    private void setSerialObjectGenerics(MqMessageListener listener) {
        ParameterizedType basicType = (ParameterizedType) listener.getClass().getGenericSuperclass();
        Type[] genericsTypes = basicType.getActualTypeArguments();
        addSerialObjectGenerics(genericsTypes[0]);
    }

    private void addSerialObjectGenerics(Type type) {
        if (type instanceof Class) {
            Class<?> c = (Class<?>) type;
            for (Class<?> item : FILTER_CLASS_LIST) {
                if (item.isAssignableFrom(c)) {
                    return;
                }
            }
        }

        if (type instanceof ParameterizedType) {
            addSerialGenericObjectGenerics(type);
        } else {
            addSerialNoGenericObjectGenerics(type);
        }
    }

    private void addSerialGenericObjectGenerics(Type type) {
        ParameterizedType basicType = (ParameterizedType) type;

        String className = basicType.getRawType().getTypeName();
        if (!SerialObjectInputStream.isExist(className)) {
            SerialObjectInputStream.add(className);
        }

        Type[] genericsTypes = basicType.getActualTypeArguments();
        if (genericsTypes.length == 0) {
            return;
        }
        addSerialObjectGenerics(genericsTypes[0]);
    }

    private void addSerialNoGenericObjectGenerics(Type type) {
        String className = type.getTypeName();
        if (SerialObjectInputStream.isExist(className)) {
            return;
        }
        SerialObjectInputStream.add(className);
        if (type instanceof Class) {
            Class<?> c = (Class<?>) type;
            if (BaseEnum.class.isAssignableFrom(c)) {
                return;
            }
            Field[] fields = c.getDeclaredFields();
            for (Field field : fields) {
                addSerialObjectGenerics(field.getType());
                addSerialObjectGenerics(field.getGenericType());
            }
        }
    }

    @Override
    public boolean isErrorStop() {
        return true;
    }
}
